package org.budo.lock.distributed.api.aop.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.budo.lock.distributed.api.annotation.DistributedLock;
import org.budo.lock.distributed.api.annotation.DistributedLock.Action;
import org.budo.lock.distributed.api.exception.LockedErrorException;
import org.budo.support.lang.util.ThreadUtil;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;

/**
 * @author limingwei2
 */
public class BudoDistributedLockInterceptor extends AbstractBudoDistributedLockInterceptor implements MethodInterceptor {
    private static final Logger log = Slf4j.getLogger();

    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("1");

        DistributedLock annotation = invocation.getMethod().getAnnotation(DistributedLock.class);
        if (null == annotation) {
            return invocation.proceed();
        }

        String target = null; // 被锁资源
        String caller = null; // 当前请求者
        // 超时 timeout
        String locker = null; // 旧的加锁人

        int trys = annotation.trys(); // 总共尝试次数
        int trysWaits = trys - 1; // 会sleep的次数
        for (int i = 0; i < trys; i++) {
            locker = this.getDistributedLockService().tryLock(target, caller);

            if (null != locker // 未获取到锁
                    && i < trysWaits) { // 除最后一次之外都等待
                ThreadUtil.sleep(annotation.waits()); // 每次等待时间
            }
        }

        if (null == locker) { // 未被锁
            Object result = invocation.proceed();
            this.getDistributedLockService().unLock(target);
            return result;
        } else { // 已被锁
            if (Action.ERROR.equals(annotation.action())) {
                throw new LockedErrorException(target + " is locked by " + locker + ", " + caller + " fail");
            } else { // FAIL
                log.info("{} is locked by {}, {} fail", target, locker, caller);
                return null;
            }
        }
    }
}
